<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>scroll bar</title>
<style>
.yscrollbar {
    position: relative;
}
.yscrollbar-content {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
}
.yscrollbar-bar {
    position: absolute;
    z-index: 600;
    top: 0;
    right: 0;
    width: 8px;
    height: 30px;
    background-color: #666;
    
    /*
    opacity: 0;
    transition: opacity .5s;
    */
    border-radius: 3px;
}
.yscrollbar:hover .yscrollbar-bar {
    opacity: 1;
}
</style>
</head>
<body>
    
    <div id="demo" class="yscrollbar" style="width: 300px; height: 400px; overflow: hidden; border: 1px solid red">
        <div>这是一行</div>
        <p>这是一行</p>
        <ul>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
            <li>这是一段文本</li>
        </ul>
    </div>

    <input type="button" onclick="document.querySelector('.yscrollbar-content').innerHTML+=document.querySelector('.yscrollbar-content').innerHTML;" value="追加内容">
    <input type="button" onclick="s.reRender()" value="重新渲染">
    
<script>
function YScrollBar() {
    this.doc = document;
    this.win = window;
    
    this.wrapper = null;
    this.content = null;
    this.bar = null;
    
    this.clientHeight = 0;
    this.scrollHeight = 0;
    this.top = 0;
    
    // drag
    this.previousY = 0;
    this.previousScrollTop = 0;
    this.barHeight = 0;
    this.wrapperHeight = 0;
    this.contentHeight = 0;
    
    this.options = {
        stepLength: 30
    };
}
YScrollBar.prototype = {
    constructor: YScrollBar,
    step: function(delta) {
        // page up -> content scroll down
        if(delta > 0) {
            this.top = this.top + this.options.stepLength;
            
            if(this.top >= 0) {
                this.top = 0;
            }
            
        } else {
            // page down -> content scroll up
            this.top = this.top - this.options.stepLength;
            
            if(this.top < -(this.scrollHeight - this.clientHeight)) {
                this.top = -(this.scrollHeight - this.clientHeight);
            }
        }
        
        this.content.style.top = this.top + 'px';
        this.bar.style.top = -this.top * this.clientHeight / this.scrollHeight + 'px';
    },
    handlerWheel: function(e) {
        if(!e) {
            e = window.event;
        }
        
        var delta = 0;
        
        if(e.wheelDelta) {
            delta = e.wheelDelta / 120;
            
        } else if(e.detail) {
            delta = -e.detail / 3;
        }
        
        this.step(delta);
    },
    initStructure: function() {
        this.content = this.doc.createElement('div');
        this.content.className = 'yscrollbar-content';
        this.content.innerHTML = this.wrapper.innerHTML;
        
        // clear
        this.wrapper.innerHTML = '';
    
        this.bar = this.doc.createElement('div');
        this.bar.className = 'yscrollbar-bar';
        
        this.wrapper.appendChild(this.bar);
        this.wrapper.appendChild(this.content);
    },
    initEvent: function() {
        var wrapper = this.wrapper;
        var self = this;
    
        // Firefox
        if(undefined === wrapper.onmousewheel) {
            wrapper.addEventListener('DOMMouseScroll', function(e) {
                self.handlerWheel(e);
            }, false);
        
        } else {
            // Main browser
            wrapper.onmousewheel = function(e) {
                self.handlerWheel(e);
            };
        }
        
        wrapper = null;
        
        // bar drag
        this.bar.onmousedown = function(e) {
            self._mouseDown(e);
        };
    },
    _mouseDown: function(e) {
        e = e || this.win.event;
        var self = this;
            
        this.previousY = e.clientY;
        this.previousScrollTop = parseInt(this.bar.style.top.replace('px', '')) || 0;
        this.barHeight = this.bar.clientHeight;
        this.wrapperHeight = this.wrapper.clientHeight;
        this.contentHeight = this.content.clientHeight;
        
        this.wrapper.onmousemove = function(e){ self._mousemove(e); };
        this.wrapper.onmouseup = function(e){ self._mouseup(e); };
    },
    _mousemove : function(e) {
        e = e || this.win.event;
        
        var distance = e.clientY - this.previousY;
        var percent = 0;
        if(this.previousScrollTop + distance <= 0) {
            distance = -this.previousScrollTop;
        }
        if(this.previousScrollTop + distance >= this.wrapperHeight - this.barHeight) {
            distance = this.wrapperHeight - this.barHeight - this.previousScrollTop;
        }
        
        percent = (this.previousScrollTop + distance) / this.wrapperHeight;
        this.top = -this.contentHeight * percent;
        
        this.bar.style.top = this.previousScrollTop + distance + 'px';
        this.content.style.top = this.top + 'px';
    },
    _mouseup : function(e) {
        this.wrapper.onmousemove = null;
    },
    getStyle: function(element) {
        if('function' === typeof this.win.getComputedStyle) {
            return this.win.getComputedStyle(element, null);
        }
        
        return element.currentStyle;
    },
    reRender: function() {
        var scrollHeight = this.scrollHeight = this.content.scrollHeight;
        var clientHeight = this.clientHeight = this.wrapper.clientHeight;
        
        this.bar.style.height = Math.floor(Math.max(2, clientHeight * clientHeight / scrollHeight)) + 'px';
    
        return this;
    },
    render: function(id) {
        this.wrapper = this.doc.getElementById(id);
        
        this.initStructure();
        this.reRender();
        this.initEvent();
        
        return this;
    }
};


var s = new YScrollBar().render('demo');
</script>
</body>
</html>













